package apps.sim.proxy;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

import apps.sim.core.log.Console;

public class ProxyThread extends Thread {

	private Socket pSocket;
	private int socketTimeout = 0;

	public ProxyThread(Socket s) {
		pSocket = s;
	}

	public void setTimeout(int timeout) {
		socketTimeout = timeout * 1000;
	}

	public void run() {
		try {
			long startTime = System.currentTimeMillis();
			BufferedInputStream clientIn = new BufferedInputStream(pSocket.getInputStream());
			BufferedOutputStream clientOut = new BufferedOutputStream(pSocket.getOutputStream());
			Socket server = null;
			byte[] request = null;
			int responseLength = 0;
			int pos = -1;
			StringBuffer host = new StringBuffer("");
			String hostName = "";
			int hostPort = 80;
			request = getHTTPData(clientIn, host, false);
			hostName = host.toString();
			pos = hostName.indexOf(":");
			if (pos > 0) {
				try {
					hostPort = Integer.parseInt(hostName.substring(pos + 1));
				} catch (Exception e) {
				}
				hostName = hostName.substring(0, pos);
			}

			SocketAddress address = new SocketAddress(hostName, hostPort, request);
			if (ProxyServer.FLAG_RESOLVE_ADDRESSES) {
				final String old = address.getAddress();
				address = SocketAddressResolver.resolve(address);
				Console.printInfo("URL Redirection: " + old + " -> " + address.getAddress());
			} else {
				Console.printInfo("URL Forward: " + address.getAddress());
			}
			try {
				server = new Socket(address.hostName, address.hostPort);
			} catch (Exception e) {
				String errMsg = "HTTP/1.0 500\nContent Type: text/plain\n\n" + "Error connecting to the server:\n" + e + "\n";
				clientOut.write(errMsg.getBytes(), 0, errMsg.length());
			}

			if (server != null) {
				server.setSoTimeout(socketTimeout);
				BufferedInputStream serverIn = new BufferedInputStream(server.getInputStream());
				BufferedOutputStream serverOut = new BufferedOutputStream(server.getOutputStream());
				serverOut.write(address.request, 0, address.requestLength);
				serverOut.flush();
				responseLength = streamHTTPData(serverIn, clientOut, true);
				serverIn.close();
				serverOut.close();
			}

			long endTime = System.currentTimeMillis();
			Console.printInfo("Request from " + pSocket.getInetAddress().getHostAddress() + " on Port " + pSocket.getLocalPort() + " to host " + hostName + ":" + hostPort + "  ("
					+ address.requestLength + " bytes sent, " + responseLength + " bytes returned, " + Long.toString(endTime - startTime) + " ms elapsed)");
			clientOut.close();
			clientIn.close();
			pSocket.close();
		} catch (Exception e) {
			System.err.println("Error in ProxyThread: " + e);
		}

	}

	private byte[] getHTTPData(InputStream in, StringBuffer host, boolean waitForDisconnect) {
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		streamHTTPData(in, bs, host, waitForDisconnect);
		return bs.toByteArray();
	}

	private int streamHTTPData(InputStream in, OutputStream out, boolean waitForDisconnect) {
		StringBuffer foo = new StringBuffer("");
		return streamHTTPData(in, out, foo, waitForDisconnect);
	}

	private int streamHTTPData(InputStream in, OutputStream out, StringBuffer host, boolean waitForDisconnect) {
		StringBuffer header = new StringBuffer("");
		String data = "";
		int responseCode = 200;
		int contentLength = 0;
		int pos = -1;
		int byteCount = 0;

		try {
			data = readLine(in);
			if (data != null) {
				header.append(data + "\r\n");
				pos = data.indexOf(" ");
				if ((data.toLowerCase().startsWith("http")) && (pos >= 0) && (data.indexOf(" ", pos + 1) >= 0)) {
					String rcString = data.substring(pos + 1, data.indexOf(" ", pos + 1));
					try {
						responseCode = Integer.parseInt(rcString);
					} catch (Exception e) {
						Console.printInfo("Error parsing response code " + rcString);
					}
				}
			}

			while ((data = readLine(in)) != null) {
				if (data.length() == 0)
					break;
				header.append(data + "\r\n");
				pos = data.toLowerCase().indexOf("host:");
				if (pos >= 0) {
					host.setLength(0);
					host.append(data.substring(pos + 5).trim());
				}
				pos = data.toLowerCase().indexOf("content-length:");
				if (pos >= 0)
					contentLength = Integer.parseInt(data.substring(pos + 15).trim());
			}
			header.append("\r\n");

			out.write(header.toString().getBytes(), 0, header.length());

			if ((responseCode != 200) && (contentLength == 0)) {
				out.flush();
				return header.length();
			}

			if (contentLength > 0)
				waitForDisconnect = false;

			if ((contentLength > 0) || (waitForDisconnect)) {
				try {
					byte[] buf = new byte[4096];
					int bytesIn = 0;
					while (((byteCount < contentLength) || (waitForDisconnect)) && ((bytesIn = in.read(buf)) >= 0)) {
						out.write(buf, 0, bytesIn);
						byteCount += bytesIn;
					}
				} catch (Exception e) {
					String errMsg = "Error getting HTTP body: " + e;
					Console.printInfo(errMsg);
				}
			}
		} catch (Exception e) {
			Console.printInfo("Error getting HTTP data: " + e);
		}
		try {
			out.flush();
		} catch (Exception e) {
		}
		final int i = header.length() + byteCount;
		return i;
	}

	private String readLine(InputStream in) {
		StringBuffer data = new StringBuffer("");
		int c;

		try {
			in.mark(1);
			if (in.read() == -1)
				return null;
			else
				in.reset();

			while ((c = in.read()) >= 0) {
				if ((c == 0) || (c == 10) || (c == 13))
					break;
				else
					data.append((char) c);
			}
			if (c == 13) {
				in.mark(1);
				if (in.read() != 10)
					in.reset();
			}
		} catch (Exception e) {
			Console.printInfo("Error getting header: " + e);
		}
		Console.printInfo("Data: " + data);
		return data.toString();
	}
}